#!/usr/bin/env python
# -*- coding: utf-8 -*-
#
# g-DrcomClient , a Novel GUI-based Drcom-Client
#
#       g-drcomclient.py
#
# Copyright (c) 2008, Henry Huang <henry.s.huang@gmail.com>

# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public
# License as published by the Free Software Foundation; either
# version 2.1 of the License, or (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
# General Public License for more details.
#
# You should have received a copy of the GNU General Public
# License along with this program; if not, write to the
# Free Software Foundation, Inc., 59 Temple Place - Suite 330,
# Boston, MA 02111-1307, USA.

import pygtk,gtk,gobject
pygtk.require('2.0')
import time,sys,gnome,pynotify,os
import gnome.ui
from os import listdir,getcwd,path
import socket,md5,fcntl,struct
from operator import xor

global conf_name
conf_name='gdrcom.conf'

global conf_path
conf_path='/home/'+os.environ['USER']+'/.gdrcom'

global sound_path
sound_path='/usr/share/gdrcom/gdrcom.wav'

global icon_path
icon_path='/usr/share/gdrcom/gdrcom.png'

global license_path
license_path='/usr/share/gdrcom/COPYING'

class drcom_client():

	def get_ifname(self):

		ifname_space=os.popen("/sbin/ifconfig -s| awk '{print $1}'").read()
		ifname_start=ifname_space.find('\n')
		ifname_name=[]
		while(ifname_start!=-1):
			ifname_end=ifname_space.find('\n',ifname_start+1)
			if ifname_end==-1:
				break
			ifname=ifname_space[ifname_start+1:ifname_end]
			ifname_name.append(ifname)
			ifname_start=ifname_end
		ifname_status=os.popen("/sbin/ifconfig | awk '{print $1,$3}'").read()
		for i in range(0,len(ifname_name)):
			ifname_start=ifname_status.find(ifname_name[i])
			if i==len(ifname_name)-1:
				ifname_end=len(ifname_status)
			else:
				ifname_end=ifname_status.find(ifname_name[i+1])

			ifname_region=ifname_status[ifname_start:ifname_end]
			ifname_index=ifname_region.find('RUNNING')
			if ifname_index!=-1 and 'lo' not in ifname_name[i]:
				return ifname_name[i]
				
		self.tag='Error 01'	
		self.balloons('No active network card!')
		self.quit_common()

	def get_ip_addr(self):
		try:
			s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
			ip_addr=fcntl.ioctl(s.fileno(),0x8915,\
				struct.pack('256s', self.ifname[:15]))[20:24]
		except:
			self.tag='Error 02'			
			self.balloons('Can not get your IP address!')
			self.quit_common()

		return ip_addr


	def get_mac_addr(self):
		try:
			s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
			mac_addr=fcntl.ioctl(s.fileno(), 0x8927,\
				struct.pack('256s', self.ifname[:15]))[18:24]
		except:
			self.tag='Error 03'
			self.balloons('Can not get your MAC address!')
			self.quit_common()
		
		return mac_addr

	def read_conf(self):
		pathname=conf_path
		if path.exists(pathname)==False:
			os.mkdir(pathname)
		file_path=path.join(pathname,conf_name)
		list_dir=listdir(pathname)
		if conf_name in list_dir:
			f=file(file_path,'r')
			account_pass=f.read()		
			f.close()
			account_end=account_pass.find(',')
			if account_end==-1:
				self.passwd_flag=False		
				self.account=''
				self.password=''
				return False
			self.account=account_pass[:account_end]
			return_index=account_pass.find('\x0a')
			if return_index==-1:
				return_index=len(account_pass)
			self.password=account_pass[account_end+1:return_index]
			self.passwd_flag=True
			return True

		self.passwd_flag=False		
		self.account=''
		self.password=''
		return False		

	def init_conf(self):
		self.BUFFER=512
		self.server_brand='Drco'
		self.server_ip='192.168.168.168'
		self.server_port=61440
		self.ifname=self.get_ifname()
		self.md5_tail='\x14\x00\x07\x0b'
		self.host_ip=self.get_ip_addr()
		self.host_ip_dec=socket.inet_ntoa(self.host_ip)
		self.host_name='\x00'*16
		self.kernel_version='\x00'*12
		self.mac_addr=self.get_mac_addr()
		self.packet_id={
			'client_start_request':'\x01\x00\x00\x04',
			'server_start_response':'\x02\x00\x00\x04',
			'client_login':'\x03\x01',
			'server_login':'\x04\x00\x00\x05\x03',
			'client_keeplive_1':'\xfe',
			'client_keeplive_2':'\xff',
			'server_keeplive':'\x4d\x26',
			'client_end_request':'\x01\x00\x06\x04',
			'server_end_response':'\x02\x00\x06\x04',
			'client_logout':'\x06\x01',
			'server_logout':'\x04\x00\x00\x05\x03',
			}
		self.keep_live_count=0

	def hex_xor(self,xor_oper1,xor_oper2,oper_len):
		xor_result=''
		for i in range(0,oper_len):
			temp=chr(xor(ord(xor_oper1[i]),ord(xor_oper2[i])))
			xor_result=xor_result+temp
		return xor_result

	def show_hex(self,hex_oper):
		for i in range(0,len(hex_oper)):
			print hex(ord(hex_oper[i])),
		print '\n'

	def show_dec(self,dec_oper):
		dec_result=''
		for i in range(0,len(dec_oper)):
			dec_hex=hex(ord(dec_oper[i]))[2:]
			dec_result=dec_result+'0'*(2-len(dec_hex))+dec_hex
		return str(int(dec_result,16))

	def md5_key(self,md5_content):
		md5_temp=md5.new()
		md5_temp.update(md5_content)
		return md5_temp.digest()

	def drcom_login(self,widget):

		try:
			if self.login_flag==[True,False]:
				self.tag='Error 04'
				self.balloons('You have already LOGIN!')
				return False
		except:
			self.login_flag=[False,False]						

		self.init_conf()	
		self.password_save()
		proc_name='client_start_request'
		self.drcom_sock=socket.socket(socket.AF_INET,socket.SOCK_DGRAM)
		try:	
			self.drcom_sock.bind((self.host_ip_dec,self.server_port))
			self.drcom_sock.settimeout(1.0)
		except:
			self.tag='Error 05'
			self.balloons('Fail to bind your address!')
			return False

		send_data=self.packet_id[proc_name]+'\x01'+'\x00'*15
		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))		
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)
			
		except:
			self.tag='Error 06'
			self.balloons('Connection lost when login [request] !')
			return False		
			
		self.service_identifier=recv_data[0][4:8]
		proc_name='client_login'
		data_head=self.packet_id[proc_name]+'\x06\x1f'
		md5_content=self.packet_id[proc_name]+self.service_identifier+self.password
		self.first_md5=self.md5_key(md5_content)
		NULL_1='\x00'*(36-len(self.account))+'\x18\x01'
		mac_length=len(self.mac_addr)
		mac_xor=self.hex_xor(self.mac_addr,self.first_md5,mac_length)
		md5_content='\x01'+self.password+self.service_identifier+'\x00'*4
		second_md5=self.md5_key(md5_content)
		NULL_2='\x00'*12
		data_front=data_head+self.first_md5+self.account+NULL_1+\
			mac_xor+second_md5+'\x02'+\
			self.host_ip+NULL_2
		third_md5=self.md5_key(data_front)[0:8]
		NULL_3='\x00'*34+'\x02'+'\x00'*3+'\x04'+'\x00'*7+'\x02'+'\x00'*3
		NULL_4='\x00'*(16*7+4)+'\x01\x00\x01\x08'+'\x00'*8
		send_data=data_front+third_md5+'\x00'*5+\
			self.host_name+NULL_3+self.kernel_version+NULL_4
		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)
		except:
			self.tag='Error 06'
			self.balloons('Connection lost when login [response] !')
			return False

		if len(recv_data[0])==5:
			self.tag='Login Failed!'
			self.balloons('Wrong account name or password!')
			self.login_flag=[False,False]
			return False
		
		self.login_flag=[True,False]

		cost_time=recv_data[0][6]+recv_data[0][5]
		cost_traffic=recv_data[0][12]+recv_data[0][11]+recv_data[0][10]+recv_data[0][9]
		cost_money=recv_data[0][14]+recv_data[0][13]
		self.service_tail=recv_data[0][23:39]
		self.tag='Login successful!'
		self.show_cost(cost_time,cost_traffic,cost_money)
		self.tray.set_tooltip('g-DrcomClient: Online')

		recv_data=self.drcom_sock.recvfrom(self.BUFFER)
		self.keep_live()
		gobject.timeout_add(20000, self.keep_live)
	
	def drcom_logout(self,widget):
		try:
			if self.login_flag==[False,True]:
				self.tag='Error 07'
				self.balloons('You have already LOGOUT!')
				return False
			if self.login_flag==[False,False]:
				self.tag='Error 08'
				self.balloons('You should LOGIN first!')
				return False
		except:
			self.tag='Error 08'
			self.balloons('You should LOGIN first!')
			return False

		proc_name='client_end_request'
		send_data=self.packet_id[proc_name]+'\x01'+'\x00'*15
		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)
		except:
			self.tag='Error 09'
			self.balloons('Connection lost when logout [request] !')
			return False

		if recv_data[0][0]=='\x4d':
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)

		self.service_identifier=recv_data[0][4:8]
		proc_name='client_logout'
		data_head=self.packet_id[proc_name]+'\x06\x1f'
		md5_content=self.packet_id[proc_name]+self.service_identifier+self.password
		fifth_md5=self.md5_key(md5_content)
		NULL_1='\x00'*27
		mac_xor=self.hex_xor(self.mac_addr,fifth_md5,len(self.mac_addr))
		send_data=self.packet_id[proc_name]+'\x00\x1f'+fifth_md5+self.account+\
			NULL_1+mac_xor+self.service_tail

		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)
		except:
			self.tag='Error 09'
			self.balloons('Connection lost when logout [response] !')
			return False	

		if recv_data[0][0]=='\x4d':
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)

		if recv_data[0][:4]!='\x04\x00\x00\x05':
			self.tag='Logout failed!'
			self.balloons('Please check!')
			return False		

		cost_time=recv_data[0][6]+recv_data[0][5]
		cost_traffic=recv_data[0][12]+recv_data[0][11]+recv_data[0][10]+recv_data[0][9]
		cost_money=recv_data[0][14]+recv_data[0][13]
		self.tag='Logout successful!'
		self.show_cost(cost_time,cost_traffic,cost_money)
		self.tray.set_tooltip('g-DrcomClient: Offline')
		try:
			self.drcom_sock.close()
		except:
			self.tag='Error 10'
			self.balloons('Fail to Close Socket!')
			self.quit_common()
			return False

		self.login_flag=[False,True]

	def keep_live(self):
		if self.login_flag==[True,False]:
			
			if self.keep_live_count>6:
				self.tag='Error 11'
				self.balloons('Connection lost when keep_live [response] !')
				self.login_flag=[False,False]
				self.keep_live_count=0
				self.tray.set_tooltip('g-DrcomClient: Offline')
				return False	

			recv_data=self.keep_live_2()
			if len(recv_data)==4:				
				self.keeplive_tag=recv_data[2:]
				self.keep_live_1()
				self.keep_live_count=0
			return True
		else:
			return False
	
	def keep_live_2(self):
		proc_name='client_keeplive_2'
		send_data=self.packet_id[proc_name]+self.first_md5+'\x00'*3+self.service_tail
		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))
		except:
			self.tag='Error 11'
			self.balloons('Connection lost when keep_live [request] !')
			self.quit_common()
		try:		
			recv_data=self.drcom_sock.recvfrom(self.BUFFER)
		except:
			recv_data=['']
			self.keep_live_count=self.keep_live_count+1			
		
		return recv_data[0]
	
	def keep_live_1(self):
		proc_name='client_keeplive_1'
		md5_content=self.packet_id[proc_name]+self.keeplive_tag[0]+'\x01'+\
			self.md5_tail+self.keeplive_tag
		fourth_md5=self.md5_key(md5_content)
		send_data=self.packet_id[proc_name]+self.keeplive_tag[0]+'\x01'\
			+fourth_md5+'\xff'+self.service_tail
		try:
			self.drcom_sock.sendto(send_data,(self.server_ip,self.server_port))
		except:
			self.tag='Error 11'
			self.balloons('Connection lost when keep_live [request] !')
			self.quit_common()

	def on_delete_event(self, widget, event=None, user_data=None):
	        widget.hide()
	        return True

	def on_window_key_release_event(self, widget, event):
	        if event.keyval == gtk.keysyms.Escape:
	            widget.hide()

	def on_tray_button_press_event(self, *args):
        	if self.window.get_property('visible'):
        	    self.window.hide()
        	else:
        	    self.window.present()

	def password_save(self):
		self.set_account()
		if self.checkbutton.get_active()==True:
			if len(self.account)==0 and len(self.password)==0:
				f=file(path.join(conf_path,conf_name),'w')
				f.write('')
				f.close()
			else:
				f=file(path.join(conf_path,conf_name),'w')
				f.write(self.account+','+self.password)
				f.close()
		else:
			f=file(path.join(conf_path,conf_name),'w')
			f.write('')
			f.close()

	
	def set_account(self):
		self.account=self.accountbox.get_text()
		self.password=self.passwordbox.get_text()

    	def get_x(self):
        	x = self.tray.get_geometry()[1][0]
        	if self.tray.get_visible()==True:
        		x += int(self.tray.get_size() / 2) 
        	else:
            		x -= int(self.tray.get_size() / 2) 
        	return x

    	def get_y(self):
        	y = self.tray.get_geometry()[1][1]
	        if self.tray.get_visible()==True:
 			y += int(self.tray.get_size() / 2) 
        	else:
            		y -= int(self.tray.get_size() / 2) 
        	return y

	def show_cost(self,cost_time,cost_traffic,cost_money):

		info='Used '+self.show_dec(cost_time)+' Min, '+self.show_dec(cost_traffic)+\
			' KB'+'\n'+'Balance '
		
		if len(str(self.show_dec(cost_money)))==4:
			info=info+self.show_dec(cost_money)[0:2]+'.'+self.show_dec(cost_money)[2:4]+' yuan.'
		if len(str(self.show_dec(cost_money)))==3:
			info=info+self.show_dec(cost_money)[0:1]+'.'+self.show_dec(cost_money)[1:3]+' yuan.'
		if len(str(self.show_dec(cost_money)))==2:
			info=info+'0'+'.'+self.show_dec(cost_money)[0:2]+' yuan.'
		if len(str(self.show_dec(cost_money)))==1:
			info=info+'0'+'.'+'0'+self.show_dec(cost_money)+' yuan.'

		self.balloons(info)		

    	def balloons(self,info):

		_notifyRealm = self.tag
    		_Urgencies = {
        		'low': pynotify.URGENCY_LOW,
        		'critical': pynotify.URGENCY_CRITICAL,
        		'normal': pynotify.URGENCY_NORMAL
        		}
		icon=None
        	x = self.get_x()
        	y = self.get_y()
		body=info
		urgency="low"
		summary=_notifyRealm
        	pynotify.init(_notifyRealm)
        	notifyInitialized = True      
        	toast = pynotify.Notification(summary, body)
        	timeout = 5000
		toast.set_timeout(timeout)
        	toast.set_urgency(_Urgencies[urgency])
        	toast.set_hint("x", x)
        	toast.set_hint("y", y)
		gnome.sound_init('localhost')
		gnome.sound_play(path.join(sound_path))
        	toast.show()
		return False

	def __init__(self):

		self.window = gtk.Window(gtk.WINDOW_TOPLEVEL)
	        self.window.set_size_request(300, 150)
	        self.window.set_title("g-DrcomClient")
		self.window.connect("key_release_event",self.on_window_key_release_event)
	        self.window.connect("delete_event", self.on_delete_event)
		self.tray = gtk.status_icon_new_from_file(icon_path)
	    	self.tray.set_tooltip('g-DrcomClient: Offline')
		self.tray.connect("activate",self.on_tray_button_press_event)
	    	self.tray.set_visible(True)

		self.read_conf()
	        vbox = gtk.VBox(False, 0)
	        self.window.add(vbox)
	        vbox.show()
	        
		hbox = gtk.HBox(False,0)
	        vbox.add(hbox)
	        hbox.show()
	
		hbox1 = gtk.HBox(False,0)
	        vbox.add(hbox1)
	        hbox1.show()
	
		label = gtk.Label("Account ")
	        hbox.pack_start(label, True, True, 0)
	        label.show()
	
	        self.accountbox = gtk.Entry()
	        self.accountbox.set_max_length(50)
	        self.accountbox.set_visibility(True)
		if self.passwd_flag==True:
			self.accountbox.set_text(self.account)
	        hbox.pack_start(self.accountbox, True, True, 0)
	        self.accountbox.show()
	
	
		label = gtk.Label("    ")
	        hbox.pack_start(label, True, True, 0)
	        label.show()
	
		label = gtk.Label("Password")
	        hbox1.pack_start(label, True, True, 0)
	        label.show()
	
	        self.passwordbox = gtk.Entry()
		self.passwordbox.set_max_length(50)
		if self.passwd_flag==True:
			self.passwordbox.set_text(self.password)
		self.passwordbox.set_visibility(False)
	        hbox1.pack_start(self.passwordbox, True, True, 0)
	        self.passwordbox.show()
	
		label = gtk.Label("    ")
	        hbox1.pack_start(label, True, True, 0)
	        label.show()
	
	        self.checkbutton = gtk.CheckButton("Save your Password")
		if self.passwd_flag==True:
			self.checkbutton.set_active(True)
		else:
			self.checkbutton.set_active(False)
		vbox.pack_start(self.checkbutton, True, True, 0)
	        self.checkbutton.show()
	
		hbox2 = gtk.HBox(False,0)
	        vbox.add(hbox2)
	        hbox2.show()
		
		button = gtk.Button('Login')
	        button.connect("clicked", self.on_tray_button_press_event)
	        button.connect("clicked", self.drcom_login)
	        hbox2.pack_start(button, True, True, 0)
	        button.show()
	
		button = gtk.Button('Logout')
	        button.connect("clicked", self.on_tray_button_press_event)
	        button.connect("clicked", self.drcom_logout)
	        hbox2.pack_start(button, True, True, 0)
	        button.show()

		button = gtk.Button('Quit')
		button.connect("clicked", self.quit)
	        hbox2.pack_start(button, True, True, 0)
	        button.show()
		
        	item_login = gtk.ImageMenuItem(gtk.STOCK_CONNECT)
		item_logout = gtk.ImageMenuItem(gtk.STOCK_DISCONNECT)
    	 	item_about = gtk.ImageMenuItem(gtk.STOCK_ABOUT)
        	item_quit = gtk.ImageMenuItem(gtk.STOCK_QUIT)

        	item_login.connect('activate', self.drcom_login)
		item_logout.connect('activate', self.drcom_logout)
        	item_about.connect('activate', self.show_about)
        	item_quit.connect('activate', self.quit)
        
        	menu = gtk.Menu()
        
           	menu.append(item_login)
        	menu.append(item_logout)
        	menu.append(item_quit)
        	menu.append(gtk.SeparatorMenuItem())
        	menu.append(item_about)
        	self.tray.connect('popup-menu', self.pop_menu, menu)
		
        	icon = gtk.gdk.pixbuf_new_from_file(icon_path)
        	self.window.set_icon(icon)

	        self.window.show()
		gtk.main()


	def pop_dialog(self, title, data):
		dialog = gtk.Dialog(title, None, 0, (gtk.STOCK_OK, gtk.RESPONSE_OK))
    		dialog.set_border_width(25)
    		dialog.set_position(gtk.WIN_POS_CENTER_ALWAYS)
    		label = gtk.Label(data)
    		dialog.vbox.pack_start(label, True, True, 0)
    		label.show()
    		if dialog.run() == gtk.RESPONSE_OK:
      			dialog.destroy()
		return True

	def show_about(self, widget):

		version = '0.0.2'        
	    	license_file = open(license_path, "r")
        	license = license_file.read()
        	license_file.close()
        	license = str(license)
        	authors = ["Henry Huang <henry.s.huang@gmail.com>",]
	        logo = gtk.gdk.pixbuf_new_from_file(icon_path)
		comments="A Novel GUI-based Drcom-Client"
        	translator_credits = "translator-credits"

        	about=gtk.AboutDialog()

                gtk.about_dialog_set_email_hook(self.__url_hook, "mailto:")
        	gtk.about_dialog_set_url_hook(self.__url_hook, "")

        	about.set_name("g-DrcomClient")
        	about.set_version(version)
        	about.set_copyright("Copyright © 2008 Henry Huang")
        	about.set_license(license)
        	about.set_website("http://g-drcomclient.sourceforge.net")
        	about.set_authors(authors)
        	about.set_translator_credits(translator_credits)
        	about.set_logo(logo)
        
        	icon = gtk.gdk.pixbuf_new_from_file(icon_path)
        	about.set_icon(icon)
               
        	about.connect("response", lambda d, r: about.destroy())
        
        	about.show_all()
		return True

	def __url_hook(self, widget, url, scheme):
        	gnome.ui.url_show_on_screen(scheme + url, widget.get_screen())	
  	
	def pop_menu(self, widget, button, time, data=None):
    		if data:
      			data.show_all()
      			data.popup(None, None, None, 3, time)
    		return True

	def quit_common(self):
		self.login_flag=[False,False]
		self.keep_live_count=0
		self.tray.set_tooltip('g-DrcomClient: Offline')
		gtk.main()		
		
	def quit(self,widget):
		gtk.main_quit()
		sys.exit(0)


def main():
	f = open("/tmp/gdrcom-log", "w")
    	while 1:
        	f.write('%s\n' % time.ctime(time.time(  )))
        	f.flush(  )
        	time.sleep(10)

if __name__ == "__main__":

    	try:
        	pid = os.fork(  )
        	if pid > 0:
        		sys.exit(0)
    	except OSError, e:
        	print >>sys.stderr, "fork #1 failed: %d (%s)" % (e.errno, e.strerror)
        	sys.exit(1)

	os.chdir("/")
	os.setsid(  )
    	os.umask(0)

    	try:
        	pid = os.fork(  )
        	if pid > 0:
        		sys.exit(0)
    	except OSError, e:
        	print >>sys.stderr, "fork #2 failed: %d (%s)" % (e.errno, e.strerror)
        	sys.exit(1)

    	drcom_client()
    	main()











